use syn::spanned::Spanned;
use syn::{ItemEnum, ItemFn, ItemStruct, ItemUnion, ItemUse, ExprStruct, ExprCall};
use syn::visit::{self, Visit};
use quote::ToTokens;
use strsim::jaro_winkler;

use crate::symboltable::{Symbol, SymbolTable};

const THRESHOLD:f64 = 0.9;

pub struct CodeChecker<'a> {
    pub symbol_table: &'a mut SymbolTable,
    pub fixed_code: String,
    pub context_code: String,
    pub errors: Vec<String>,
    pub crate_symbol_table: &'a mut SymbolTable,
    pub added_use_statements: Vec<String>,
}

impl<'a> CodeChecker<'a> {
    fn check_struct_fields(&mut self, struct_name: &str, expr_struct: &ExprStruct, fields: &SymbolTable) {
        //println!("Get struct symbol:{:?}", self.symbol_table.get(struct_name));
        let threshold_field = 0.8;
        //if let Some(fields) = self.symbol_table.get_struct_fields(struct_name) {
        let mut used_fields = vec![];
        for field in &expr_struct.fields {
            let field_name = field.member.to_token_stream().to_string();
            //println!("Field name:{}", field_name);
            if fields.get(&field_name).is_none() {
                let field_type = field.expr.to_token_stream().to_string();
                if let Some((closest, similarity)) = find_closest_field(&field_name, threshold_field, fields) {
                    self.errors.push(format!("Field '{}' not found in '{}'. Did you mean '{}' (similarity: {:.2})?", field_name, struct_name, closest, similarity));
                    self.update_code(&field_name, &closest, field.member.span());
                    used_fields.push(closest.clone());
                    //self.fixed_code = self.fixed_code.replace(&field_name, &closest);
                } else {
                    self.errors.push(format!("Field '{}' not found in '{}'", field_name, struct_name));
                }
            } else {
                used_fields.push(field_name);
            }
        }

        let empty_symbol_table = SymbolTable::new();
        let struct_fields = self.symbol_table.get_struct_fields(struct_name).unwrap_or(&empty_symbol_table);
        let unused_fields: Vec<_> = struct_fields.symbols.keys()
            .filter(|key| !used_fields.contains(&key.to_string()))
            .collect();

        if unused_fields.len() == 1 && expr_struct.fields.len() < struct_fields.symbols.len() {
            let missing_field = unused_fields[0];
            if let Some(Symbol::StructField {ty, ..}) = struct_fields.get(missing_field) {
                self.errors.push(format!("Field '{}' is missing in struct '{}'.", missing_field, struct_name));
                let new_field = format!("{}: {}", missing_field, ty);
                self.fixed_code.push_str(&format!("\n{};", new_field));
            }
        }
    }

    fn update_code(&mut self, old_name: &str, new_name: &str, span: proc_macro2::Span) {
        let start = span.start();
        //let end = span.end();
        let lines: Vec<&str> = self.fixed_code.lines().collect();
        let line = lines[start.line - 1];
        let updated_line = line.replace(old_name, new_name);
        self.fixed_code = self.fixed_code.lines()
            .enumerate()
            .map(|(i, l)| if i == start.line - 1 {updated_line.clone()} else {l.to_string()})
            .collect::<Vec<String>>()
            .join("\n");
        
        // Parse the updated code
        let syntax_tree = syn::parse_file(&self.fixed_code).expect("Unable to parse file");
        self.errors.clear();
        self.visit_file(&syntax_tree);
    }

    fn add_missing_use_statement(&mut self, crate_name: &str) {
        if let Some(Symbol::Crate {use_stmt}) = self.crate_symbol_table.get(crate_name) {
            if !self.added_use_statements.contains(use_stmt) {
                self.added_use_statements.push(use_stmt.clone());
            }
            //self.fixed_code = format!("{}\n{}", use_stmt, self.fixed_code);
            self.context_code = format!("{}\n{}", use_stmt, self.context_code);
            self.symbol_table.insert(crate_name.to_string(), Symbol::Crate { use_stmt: use_stmt.clone()});
            // Parse the updated code
            let syntax_tree = syn::parse_file(&self.fixed_code).expect("Unable to parse file");
            self.errors.clear();
            self.visit_file(&syntax_tree);
        }
    }

}

impl<'ast> Visit<'ast> for CodeChecker<'_> {
    fn visit_item_fn(&mut self, node: &'ast ItemFn) {
        let fn_name = node.sig.ident.to_string();
        if self.symbol_table.get(&fn_name).is_none() {
            if let Some((closest, _similarity)) = self.symbol_table.find_closest(&fn_name, THRESHOLD) {
                self.errors.push(format!("Function '{}' not found. Did you mean '{}'?", fn_name, closest));
                //self.fixed_code = self.fixed_code.replace(&fn_name, &closest);
                self.update_code(&fn_name, &closest, node.sig.ident.span());
            } else {
                self.errors.push(format!("Function '{}' is not defined in the symbol table.", fn_name));
            }
        } else {
            visit::visit_block(self, &node.block);
        }
        //visit::visit_item_fn(self, node);
    }

    fn visit_path(&mut self, node: &'ast syn::Path) {
        let path_str = node.to_token_stream().to_string();
        if self.symbol_table.get(&path_str).is_none() {
            if let Some((closest, _similarity)) = self.symbol_table.find_closest(&path_str, THRESHOLD) {
                self.errors.push(format!("Symbol '{}' not found. Did you mean '{}'?", path_str, closest));
                //self.fixed_code = self.fixed_code.replace(&path_str, &closest);
                self.update_code(&path_str, &closest, node.span());
            } else {
                self.errors.push(format!("Symbol '{}' is not defined in the symbol table.", path_str));
            }
        }
        visit::visit_path(self, node);
    }

    fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
        let struct_name = node.ident.to_string();
        if self.symbol_table.get(&struct_name).is_none() {
            if let Some((closest, _similarity)) = self.symbol_table.find_closest(&struct_name, THRESHOLD) {
                self.errors.push(format!("Struct '{}' not found. Did you mean '{}'?", struct_name, closest));
                //self.fixed_code = self.fixed_code.replace(&struct_name, &closest);
                self.update_code(&struct_name, &closest, node.ident.span());
            } else {
                self.errors.push(format!("Struct '{}' is not defined in the symbol table.", struct_name));
            }
        }
        visit::visit_item_struct(self, node);
    }

    fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
        let enum_name = node.ident.to_string();
        if self.symbol_table.get(&enum_name).is_none() {
            if let Some((closest, _similarity)) = self.symbol_table.find_closest(&enum_name, THRESHOLD) {
                self.errors.push(format!("Enum '{}' not found. Did you mean '{}'?", enum_name, closest));
                //self.fixed_code = self.fixed_code.replace(&enum_name, &closest);
                self.update_code(&enum_name, &closest, node.ident.span());
            } else {
                self.errors.push(format!("Enum '{}' is not defined in the symbol table.", enum_name));
            }
        }
        visit::visit_item_enum(self, node);
    }

    fn visit_item_union(&mut self, node: &'ast ItemUnion) {
        let union_name = node.ident.to_string();
        if self.symbol_table.get(&union_name).is_none() {
            if let Some((closest, _similarity)) = self.symbol_table.find_closest(&union_name, THRESHOLD) {
                self.errors.push(format!("Union '{}' not found. Did you mean '{}'?", union_name, closest));
                //self.fixed_code = self.fixed_code.replace(&union_name, &closest);
                self.update_code(&union_name, &closest, node.ident.span());
            } else {
                self.errors.push(format!("Union '{}' is not defined in the symbol table.", union_name));
            }
        }
        visit::visit_item_union(self, node);
    }

    fn visit_expr_struct(&mut self, node: &'ast ExprStruct) {
        let struct_name = node.path.segments.last().unwrap().ident.to_string();
        if self.symbol_table.get(&struct_name).is_none() {
            if let Some((closest, similarity)) = self.symbol_table.find_closest(&struct_name, THRESHOLD) {
                self.errors.push(format!("Struct '{}' not found. Did you mean '{}' (similarity: {:.2})?", struct_name, closest, similarity));
                self.update_code(&struct_name, &closest, node.path.span());
            } else {
                self.errors.push(format!("Struct '{}' is not defined in the symbol table.", struct_name));
            }
        } else {
            //if let Some(Symbol::Struct { fields, .. }) = self.symbol_table.get(&struct_name) {
            //if let Some(Symbol::Struct { fields, .. }) = self.get_symbol_table().get(&struct_name) {
            //    self.check_struct_fields(&struct_name, node, fields);
            //}
            let field_symbol_table = self.symbol_table.get_struct_fields(&struct_name).cloned().unwrap_or_else(SymbolTable::new);
            self.check_struct_fields(&struct_name, node, &field_symbol_table);
        }
        visit::visit_expr_struct(self, node);
    }

    fn visit_item_use(&mut self, node:&'ast ItemUse) {
        let use_stmt = node.to_token_stream().to_string();
        if use_stmt.contains("::{") {
            let base = use_stmt.split("::{").next().unwrap().to_string();
            let inner = use_stmt.split("::{").nth(1).unwrap().split("}").next().unwrap();
            let items: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
            for item in items {
                let use_path = format!("{}::{};", base, item);
                let crate_name = item.to_string();
                if let Some(Symbol::Crate {use_stmt: existing_use_stmt}) = self.symbol_table.get(&crate_name) {
                    if *existing_use_stmt != use_path {
                        self.symbol_table.insert(crate_name.clone(), Symbol::Crate {use_stmt: use_path.clone()});
                        self.added_use_statements.push(use_path);
                    }
                } else {
                    self.symbol_table.insert(crate_name.clone(), Symbol::Crate { use_stmt: use_path.clone()});
                    self.added_use_statements.push(use_path);
                }
            }
        } else {
            let mut use_path = use_stmt.split("::").map(str::to_string).collect::<Vec<String>>();
            let last_segment = use_path.pop().unwrap().replace(";", "");
            let crate_name = if last_segment.starts_with('{') {
                use_path.join("::")
            } else {
                last_segment.clone()
            };

            if let Some(Symbol::Crate {use_stmt: existing_use_stmt}) = self.symbol_table.get(&crate_name) {
                if *existing_use_stmt != use_stmt {
                    self.symbol_table.insert(crate_name.clone(), Symbol::Crate { use_stmt: use_stmt.clone()});
                    self.added_use_statements.push(use_stmt.clone());
                }
            } else {
                self.symbol_table.insert(crate_name.clone(), Symbol::Crate { use_stmt: use_stmt.clone()});
                self.added_use_statements.push(use_stmt.clone());
            }
        }
        visit::visit_item_use(self, node);
    }

    fn visit_expr_call(&mut self, node: &'ast ExprCall) {
        if let Some(path) = node.func.to_token_stream().to_string().split("::").next() {
            let crate_name = path.trim().to_string();
            //println!("Expr call crate:{}!", crate_name);
            //println!("global symbol table:{:?}", self.symbol_table.get(&crate_name));
            //println!("crate symbol table:{:?}", self.crate_symbol_table.get(&crate_name));
            if self.symbol_table.get(&crate_name).is_none() && !self.crate_symbol_table.get(&crate_name).is_none() {
                self.add_missing_use_statement(&crate_name);
            }
        }
    }
}

fn find_closest_field(field_name: &str, threshold: f64, symbol_table: &SymbolTable) -> Option<(String, f64)> {
    let closest = symbol_table.symbols.keys()
        .filter_map(|key| {
            let similarity = jaro_winkler(key, field_name);
            //println!("{}<->{}:{}", key, field_name, similarity);
            Some((key.clone(), similarity))
        })
        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap());

    if let Some((ref key, similarity)) = closest {
        if similarity >= threshold {
            //println!("Closest field match(>{}) is '{}' with similarity {:.2}", threshold, key, similarity);
            Some((key.clone(), similarity))
        } else {
            //println!("Closest field match(<={}) is '{}' with similarity {:.2}", threshold, key, similarity);
            None
        }
    } else {
        None
    }
}
